package com.demo.service.impl;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.demo.constant.CacheConstants;
import com.demo.domain.SysDict;
import com.demo.enums.DeletedEnum;
import com.demo.enums.DictStatusEnum;
import com.demo.excetion.ServiceException;
import com.demo.mapper.SysDictMapper;
import com.demo.service.SysDictService;
import com.demo.utils.BeanUtils;
import com.demo.utils.PageUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author gxd
 * @since 2025/5/28
 */
@Service
@Slf4j
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 分页列表-字典列表
     * @param dict 字典查询条件
     * @return 字典集合
     */
    @Override
    public List<SysDict> pageList(SysDict dict) {
        long l1 = System.currentTimeMillis();

        LambdaQueryWrapper<SysDict> lqw = Wrappers.lambdaQuery(SysDict.class)
                .eq(SysDict::getDeleted, DeletedEnum.NO.getCode())
                .eq(SysDict::getStatus, DictStatusEnum.OK.getCode())
                .orderByAsc(SysDict::getSort);

        List<SysDict> all = new ArrayList<>();
        //编写分页 并发多线程查询
        long count = this.count(lqw);
        int rowCount = 3000;
        List<Integer> pageCountList = PageUtils.manualPage(count, rowCount);

        //这个是 自带的 线程池
        pageCountList.parallelStream().forEach(pageCount -> {
            Page<SysDict> page = this.page(new Page<>(pageCount, rowCount));
            List<SysDict> dictList = ObjectUtil.defaultIfNull(page.getRecords(), new ArrayList<SysDict>());
            all.addAll(dictList);
        });

        // 或者用 自定义的线程池，多线程并发编写
        // ExecutorService fixedThreadPool = Executors.newFixedThreadPool(20);
        // List<CompletableFuture<Void>> allList = new ArrayList<>();
        // pageCountList.forEach(pageCount -> {
        //     CompletableFuture<Void> a = CompletableFuture.runAsync(() -> {
        //         Page<SysDict> page = this.page(new Page<>(pageCount, rowCount), lqw);
        //         List<SysDict> dictList = ObjectUtil.defaultIfNull(page.getRecords(), new ArrayList<SysDict>());
        //         all.addAll(dictList);
        //     }, fixedThreadPool);
        //     allList.add(a);
        // });
        //
        // CompletableFuture.allOf(allList.toArray(new CompletableFuture[0])).join();

        long l2 = System.currentTimeMillis();
        // 原始：           1138 1138 1160
        // parallelStream：1130 1139 1173
        // 自定义线程池：     1338 1356 1246
        // 可能是你的这个数据不够多看不出来变化
        // 如果遇到慢的时候就会体现出来多线程的优点，总而言之数据太少
        // 数据多的话可以通过自己计算拆分为阶段性查询，然后使用多线程查询，然后使用归并join然后合并起来就好了
        log.info("--------字典耗时time----------{}", (l2 - l1)  );
        return all;
    }

    /**
     * 不分页列表-字典列表
     * @param dict 字典查询条件
     * @return 字典集合
     */
    @Override
    public List<SysDict> selectDictList(SysDict dict) {
        return this.list(
                Wrappers.lambdaQuery(SysDict.class)
                        .eq(SysDict::getDeleted, DeletedEnum.NO.getCode())
                        .like(StrUtil.isNotBlank(dict.getCode()), SysDict::getCode, dict.getCode())
                        .like(StrUtil.isNotBlank(dict.getName()), SysDict::getName, dict.getName())
                        .eq(ObjUtil.isNotNull(dict.getStatus()), SysDict::getStatus, dict.getStatus())
                        .orderByAsc(SysDict::getSort)
        );
    }

    /**
     * 详情信息-根据Id获取字典详情信息
     * @param id 字典Id
     * @return 字典
     */
    @Override
    public SysDict getDictById(Long id) {
        SysDict sysDict = (SysDict) redisTemplate.opsForValue().get(CacheConstants.DICT_ONE_KEY + id);
        if (ObjUtil.isNull(sysDict)) {
            sysDict = this.getById(id);
            redisTemplate.opsForValue().set(CacheConstants.DICT_ONE_KEY + id, sysDict);
        }
        return sysDict;
    }

    /**
     * 添加或修改-字典信息
     * @param dict 字典实体
     * @return 字典
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SysDict addOrEdit(SysDict dict) {
        Assert.notNull(dict, "字典信息不能为空");
        // 使用延迟双删
        if (ObjUtil.isNotNull(dict.getId())) {
            Long id = dict.getId();
            boolean lockAcquired = false;
            String lockKey = CacheConstants.DICT_LOCK_KEY + id;
            try {
                lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "给该字典加锁", 10, TimeUnit.SECONDS);

                if (Boolean.FALSE.equals(lockAcquired)) {
                    throw new ServiceException("当前字典正在被操作，请重试");
                }
                // 删除单条缓存，分组和树不处理
                cleanDictCache(id);

                // 更新数据
                SysDict dictInfo = Assert.notNull(this.getById(id), "要修改的用户信息不存在");
                BeanUtils.copyNotEmptyProperties(dict, dictInfo);
                this.updateById(dictInfo);

                // 异步延迟删除和更新
                CompletableFuture.runAsync(() -> {
                    try {
                        Thread.sleep(400);
                        // 删除单套缓存，分组和树不处理
                        cleanDictCache(id);

                        // 更新单条数据
                        redisTemplate.opsForValue().set(CacheConstants.DICT_ONE_KEY + id, dictInfo);
                        // 判断缓存中是否分组，有：取出缓存的数据把修改的信息替换，没有：去查询数据库
                        updateDictGroupCache(dictInfo);
                        // 判断缓存中是否有树，有：取出缓存中的树替换修改的信息，没有，去查询数据库
                        updateDictTreeCache(dictInfo);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                });
                return dictInfo;
            } finally {
                redisTemplate.delete(lockKey);
            }
        }
        // 新增数据
        dict.setDeleted(DeletedEnum.NO.getCode());
        this.save(dict);
        
        // 缓存单条
        redisTemplate.opsForValue().set(CacheConstants.DICT_ONE_KEY + dict.getId(), dict);
        // 异步延迟更新组和树缓存
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(400);
                // 更新组缓存
                updateDictGroupCache(dict);
                // 更新树缓存
                updateDictTreeCache(dict);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        return dict;
    }

    /**
     * 更新字典组缓存
     */
    private void updateDictGroupCache(SysDict dictInfo) {
        if (StrUtil.isNotBlank(dictInfo.getParentCode())) {
            String dictGroupKey = CacheConstants.DICT_GROUP_KEY + dictInfo.getParentCode();
            if (redisTemplate.hasKey(dictGroupKey)) {
                List<SysDict> dictList = (List<SysDict>) redisTemplate.opsForValue().get(dictGroupKey);

                // 修改
                Boolean updated = dictList.stream()
                        .filter(sysDict -> ObjUtil.equals(dictInfo.getId(), sysDict.getId()))
                        .findFirst()
                        .map(sysDict -> {
                            dictList.set(dictList.indexOf(sysDict), dictInfo);
                            return true;
                        })
                        .orElse(false);

                // 新增
                if (!updated) {
                    dictList.add(dictInfo);
                }

                redisTemplate.opsForValue().set(dictGroupKey, dictList);
            } else {
                redisTemplate.opsForValue().set(dictGroupKey, buildDictGroup(dictInfo.getParentCode()));
            }
        }
    }

    /**
     * 更新字典树缓存
     */
    private void updateDictTreeCache(SysDict dictInfo) {
        String treeKey = CacheConstants.DICT_TREE_KEY;
        if (redisTemplate.hasKey(treeKey)) {
            // 从缓存获取数据，树list转为平铺list替换值, 将平铺list转为树list
            List<SysDict> dictTreeList = (List<SysDict>) redisTemplate.opsForValue().get(treeKey);
            List<SysDict> dictList = flattenTree(dictTreeList);

            // 修改
            Boolean updated = dictList.stream()
                    .filter(sysDict -> ObjUtil.equals(dictInfo.getId(), sysDict.getId()))
                    .findFirst()
                    .map(sysDict -> {
                        dictList.set(dictList.indexOf(sysDict), dictInfo);
                        return true;
                    })
                    .orElse(false);

            // 添加
            if (!updated) {
                dictList.add(dictInfo);
            }

            // 重置子级
            dictList.replaceAll(sysDict ->  {
                sysDict.setChildList(null);
                return sysDict;
            });

            redisTemplate.opsForValue().set(treeKey, buildDictTree(dictList));
        } else {
            redisTemplate.opsForValue().set(treeKey, buildDictTree());
        }
    }

    /**
     * 将树结构的list转为平铺的list
     */
    private List<SysDict> flattenTree(List<SysDict> dictTreeList) {
        return dictTreeList.stream()
                .flatMap(dict -> Stream.concat(Stream.of(dict), flattenTree(dict.getChildList()).stream()))
                .collect(Collectors.toList());
    }

    /**
     * 删除-字典信息（支持批量）
     * @param ids 字典Id（多个需逗号分隔）
     * @return 结果
     */
    @Override
    public boolean batchRemoveById(String ids) {
        Assert.notEmpty(ids, "缺少必要参数：请提供要删除的记录 ID");
        Arrays.stream(ids.split(StrPool.COMMA)).forEach(id -> {
            boolean lockAcquired = false;
            String lockKey = CacheConstants.DICT_LOCK_KEY + id;
            try {
                lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "给该字典加锁", 30, TimeUnit.SECONDS);

                if (Boolean.FALSE.equals(lockAcquired)) {
                    throw new ServiceException("当前字典正在被操作，请重试");
                }

                Long dictId = Long.valueOf(id);
                cleanDictCache(dictId);

                this.update(Wrappers.lambdaUpdate(SysDict.class)
                        .set(SysDict::getDeleted, DeletedEnum.YES.getCode()).eq(SysDict::getId, dictId));

                CompletableFuture.runAsync(() -> {
                    try {
                        Thread.sleep(400);
                        cleanDictCache(dictId);

                        SysDict sysDict = this.getById(dictId);
                        // 更新单条数据
                        redisTemplate.opsForValue().set(CacheConstants.DICT_ONE_KEY + dictId, sysDict);
                        // 更新组缓存
                        updateDictGroupCache(sysDict);
                        // 更新树缓存
                        updateDictTreeCache(sysDict);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                });
            } finally {
                redisTemplate.delete(lockKey);
            }
        });
        return true;
    }

    /**
     * 修改状态-启用或禁用
     * @param id 字典Id
     * @param status 状态
     * @return 结果
     */
    @Override
    public boolean editStatus(Long id, Integer status) {
        Assert.notNull(id, "缺少必要参数：请提交要修改的记录 ID");
        Assert.notNull(status, "缺少必要参数：请提交要修改的记录 状态");

        boolean lockAcquired = false;
        String lockKey = CacheConstants.DICT_LOCK_KEY + id;

        try {
            lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "给该字典上锁", 5, TimeUnit.SECONDS);

            if (Boolean.FALSE.equals(lockAcquired)) {
                throw new ServiceException("当前字典正在被操作，请重试");
            }

            cleanDictCache(id);

            boolean result = this.update(Wrappers.lambdaUpdate(SysDict.class)
                    .set(SysDict::getStatus, status).eq(SysDict::getId, id));

            CompletableFuture.runAsync(() -> {
                try {
                    Thread.sleep(400);
                    cleanDictCache(id);

                    SysDict sysDict = this.getById(id);
                    // 更新单条数据
                    redisTemplate.opsForValue().set(CacheConstants.DICT_ONE_KEY + id, sysDict);
                    // 更新组缓存
                    updateDictGroupCache(sysDict);
                    // 更新树缓存
                    updateDictTreeCache(sysDict);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });

            return result;
        } finally {
            redisTemplate.delete(lockKey);
        }
    }

    /**
     * 从数据库查询，构建字典组
     * @param code 编码
     * @return 字典组
     */
    public List<SysDict> buildDictGroup(String code) {
        return this.list(Wrappers.lambdaQuery(SysDict.class)
                .eq(SysDict::getParentCode, code)
                .eq(SysDict::getDeleted, DeletedEnum.NO.getCode()));
    }

    /**
     * 树查询-字典树
     * @return 字典树
     */
    @Override
    public List<SysDict> getDictTree() {
        // 从redis缓存获取字典树
        List<SysDict> dictList = (List<SysDict>) redisTemplate.opsForValue().get(CacheConstants.DICT_TREE_KEY);
        if (ObjUtil.isNull(dictList)) {
            redisTemplate.opsForValue().set(CacheConstants.DICT_TREE_KEY, buildDictTree());
        }
        return dictList;
    }

    /**
     * 从数据库查询，构建字典树
     * @return 字典树
     */
    public List<SysDict> buildDictTree() {
        return buildDictTree(pageList(new SysDict()));
    }

    /**
     * 构建字典树
     */
    private static List<SysDict> buildDictTree(List<SysDict> dictList) {
        // 分组
        Map<String, List<SysDict>> dictGroupMap = dictList.stream()
                .filter(dict -> StrUtil.isNotBlank(dict.getParentCode()))
                .collect(Collectors.groupingBy(SysDict::getParentCode));
        // 组装子级字典集合
        dictList.forEach(dict -> dict.setChildList(dictGroupMap.getOrDefault(dict.getCode(), new ArrayList<>())));
        // 输出字典树
        return dictList.stream()
                .filter(dict -> ObjUtil.isNull(dict.getParentCode()))
                .collect(Collectors.toList());
    }

    /**
     * 清除单条字典缓存
     */
    public void cleanDictCache(Long id) {
        if (ObjUtil.isNotNull(id)) {
            redisTemplate.delete(CacheConstants.DICT_ONE_KEY + id);
        }
    }

    /**
     * 加载字典数据到Redis缓存
     */
    @Override
    public void loadingDictCache() {
        // 平铺存储：key：sys:dict:one:{id}, value：字典对象 ; key：sys:dict:child:one:{id}, value：字典对象
        long t1 = System.currentTimeMillis();
        List<SysDict> dictList = this.pageList(new SysDict());
        Map<String, Object> dictMap = dictList.stream()
                .collect(Collectors.toMap(
                        dict -> CacheConstants.DICT_ONE_KEY + dict.getId(),
                        Function.identity()
                ));
        redisTemplate.opsForValue().multiSet(dictMap);
        long t2 = System.currentTimeMillis();
        log.info("--------字典写入redis耗时----------{}", (t2 - t1)  );
        // 父节点分组存储：key：sys:dict:group:{code}, value:字典及字典子级
        Map<String, List<SysDict>> groupMap = dictList.stream()
                .filter(dict -> StrUtil.isNotBlank(dict.getParentCode()))
                .collect(Collectors.groupingBy(SysDict::getParentCode));
        groupMap.forEach((code, list) -> redisTemplate.opsForValue().set(CacheConstants.DICT_GROUP_KEY + code, list));
        // 树形结构存储：key：sys:dict:tree, value: 字典及字典子级树结构json
   /*     // 方式一：递归
        // 注意：递归有宕机风险，死循环或者是大数据量的话,递归会宕机，因为递归的太深太多，会瞬间压栈，压栈宕机,是有一个伐值的,达到这个伐值就会报错宕机
        List<SysDict> tree = dictList.stream()
                .filter(dict -> ObjUtil.isNull(dict.getParentCode()))
                .map(dict -> {
                    dict.setChildList(getChildList(dict, dictList));
                    return dict;
                })
                .collect(Collectors.toList());
        redisTemplate.opsForValue().set(CacheConstants.DICT_TREE_KEY, tree);*/

        // 方式二：推荐使用
        dictList.forEach(d -> d.setChildList(groupMap.getOrDefault(d.getCode(), new ArrayList<>())));
        List<SysDict> treeList = dictList.stream()
                .filter(dict -> ObjUtil.isNull(dict.getParentCode()))
                .collect(Collectors.toList());
        redisTemplate.opsForValue().set(CacheConstants.DICT_TREE_KEY, treeList);
    }

    /**
     * 获取子级集合
     */
    private List<SysDict> getChildList(SysDict sysDict, List<SysDict> dictList) {
        return dictList.stream()
                .filter(dict -> ObjUtil.equals(sysDict.getCode(), dict.getParentCode()))
                .map(dict -> {
                    dict.setChildList(getChildList(dict, dictList));
                    return dict;
                })
                .collect(Collectors.toList());
    }

    /**
     * 根据code和label获取value
     */
    @Override
    public String selectNameByCodeAndLabel(String code, String label) {
        return getDictListByCode(code).stream()
                .filter(dict -> ObjUtil.equals(code, dict.getParentCode()) && ObjUtil.equals(label, dict.getLabel()))
                .map(SysDict::getName)
                .findFirst()
                .orElse(null);
    }

    /**
     * 提取公共方法
     * 从Redis中根据key获取字典集合，如果没有，去数据库查询，存入Redis
     */
    private List<SysDict> getDictListByCode(String code) {
        String key = CacheConstants.DICT_GROUP_KEY + code;
        List<SysDict> dictList = (List<SysDict>) redisTemplate.opsForValue().get(key);
        if (ObjUtil.isEmpty(dictList)) {
            dictList = this.list(Wrappers.lambdaQuery(SysDict.class)
                    .eq(SysDict::getDeleted, DeletedEnum.NO.getCode())
                    .eq(SysDict::getParentCode, code));
            redisTemplate.opsForValue().set(key, dictList);
        }
        return dictList;
    }

    /**
     * 根据code获取字典Map
     */
    @Override
    public Map<String, String> getDictMapByCode(String code) {
        return getDictListByCode(code).stream()
                .collect(Collectors.toMap(dict -> dict.getCode(), dict -> dict.getName()));
    }
}
